// SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
//
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL

#include "VideoStream.h"

#include <algorithm>
#include <condition_variable>

#include <QDateTime>
#include <QQueue>

#include <freerdp/freerdp.h>
#include <freerdp/peer.h>

#include "NetworkDetection.h"
#include "PeerContext_p.h"
#include "Session.h"

#include "krdp_logging.h"

namespace KRdp
{

namespace clk = std::chrono;

// Maximum number of frames to contain in the queue.
constexpr qsizetype MaxQueueSize = 10;

wStream* peer_stream_init(PeerContext* context)
{
	Stream_Clear(context->s);
	Stream_SetPosition(context->s, 0);
	return context->s;
}

void peer_begin_frame(freerdp_peer* client)
{
	rdpUpdate* update = client->update;
	SURFACE_FRAME_MARKER fm = { 0 };
	PeerContext* context = (PeerContext*)client->context;
	fm.frameAction = SURFACECMD_FRAMEACTION_BEGIN;
	fm.frameId = context->frame_id;
	update->SurfaceFrameMarker(update->context, &fm);
}

void peer_end_frame(freerdp_peer* client)
{
	rdpUpdate* update = client->update;
	SURFACE_FRAME_MARKER fm = { 0 };
	PeerContext* context = (PeerContext*)client->context;
	fm.frameAction = SURFACECMD_FRAMEACTION_END;
	fm.frameId = context->frame_id;
	update->SurfaceFrameMarker(update->context, &fm);
	context->frame_id++;
}

BOOL peer_draw_background(freerdp_peer* client,const VideoFrame& videoframe)
{
    //printf("peer_draw_background\n");
	int size;
	wStream* s;
	RFX_RECT rect;
	// BYTE* rgb_data;
	rdpUpdate* update = client->update;
	SURFACE_BITS_COMMAND cmd = { 0 };
	PeerContext* context = (PeerContext*)client->context;
	BOOL ret = FALSE;

	if (!client->settings->RemoteFxCodec && !client->settings->NSCodec)
		return FALSE;

	s = peer_stream_init(context);
	rect.x = 0;
	rect.y = 0;
	rect.width = client->settings->DesktopWidth;
	rect.height = client->settings->DesktopHeight;
	size = rect.width * rect.height * 3;
	//printf("%d %d\n", rect.width, rect.height);

	// if (!(rgb_data = (BYTE*)malloc(size)))
	// {
	// 	printf("Problem allocating memory\n");
	// 	return FALSE;
	// }
	// memset(videoframe, rand() %256, size);

	if (client->settings->RemoteFxCodec)
	{
		//printf("Using RemoteFX codec\n");
		if (!rfx_compose_message(context->rfx_context, s, &rect, 1, (BYTE*)videoframe.data.data(), rect.width,
		                         rect.height, rect.width * 3))
		{
			goto out;
		}

		cmd.bmp.codecID = client->settings->RemoteFxCodecId;
		cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
	}
	else
	{
		printf("Using NSCodec\n");
		nsc_compose_message(context->nsc_context, s, (BYTE*)videoframe.data.data(), rect.width, rect.height,
		                    rect.width * 3);
		cmd.bmp.codecID = client->settings->NSCodecId;
		cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
	}

	cmd.destLeft = 0;
	cmd.destTop = 0;
	cmd.destRight = rect.width;
	cmd.destBottom = rect.height;
	cmd.bmp.bpp = 32;
	cmd.bmp.flags = 0;
	cmd.bmp.width = rect.width;
	cmd.bmp.height = rect.height;
	cmd.bmp.bitmapDataLength = Stream_GetPosition(s);
	cmd.bmp.bitmapData = Stream_Buffer(s);
	peer_begin_frame(client);
	update->SurfaceBits(update->context, &cmd);
	peer_end_frame(client);
	ret = TRUE;
out:
	// free(rgb_data);
	return ret;
}


class  VideoStream::Private
{
public:

    Session *session;

    uint32_t frameId = 0;

    bool enabled = false;

    std::jthread frameSubmissionThread;
    std::mutex frameQueueMutex;

    QQueue<VideoFrame> frameQueue;

    int maximumFrameRate = 30;
    int requestedFrameRate = 30;

};

VideoStream::VideoStream(Session *session)
    : QObject(nullptr)
    , d(std::make_unique<Private>())
{
    d->session = session;
}

VideoStream::~VideoStream()
{
}

bool VideoStream::initialize()
{
    if(!m_initialized)
    {
        
        d->frameSubmissionThread = std::jthread([this](std::stop_token token)
                                                {
        while (!token.stop_requested()) {
            {   
                QImage img("/home/dll/1080.png");
                if (img.format() != QImage::Format_RGB888)
                    img = img.convertToFormat(QImage::Format_RGB888);
                KRdp::VideoFrame frame;
                frame.size = img.size();
                frame.data =  QByteArray(reinterpret_cast<const char*>(img.bits()), img.sizeInBytes());
                frame.presentationTimeStamp = std::chrono::system_clock::now();

                queueFrame(frame);
                std::unique_lock lock(d->frameQueueMutex);
                if (!d->frameQueue.isEmpty()) {
                    sendFrame(d->frameQueue.takeFirst());
                }
            }

            std::this_thread::sleep_for(std::chrono::milliseconds(1000) / d->requestedFrameRate);
        } });

        qDebug() << "Video stream initialized";
        m_initialized = true;
    }
    return m_initialized;
}

void VideoStream::close()
{
    if (d->frameSubmissionThread.joinable()) {
        d->frameSubmissionThread.request_stop();
        d->frameSubmissionThread.join();
    }

    Q_EMIT closed();
}

void VideoStream::queueFrame(const KRdp::VideoFrame &frame)
{
    if (d->session->state() != Session::State::Streaming || !d->enabled) {
        return;
    }

    std::lock_guard lock(d->frameQueueMutex);
    d->frameQueue.append(frame);

    while (d->frameQueue.size() > MaxQueueSize) {
        d->frameQueue.pop_front();
    }
}

bool VideoStream::enabled() const
{
    return d->enabled;
}

void VideoStream::setEnabled(bool enabled)
{
    if (d->enabled == enabled) {
        return;
    }

    d->enabled = enabled;
    Q_EMIT enabledChanged();
}

uint32_t VideoStream::requestedFrameRate() const
{
    return d->requestedFrameRate;
}

void VideoStream::sendFrame(const VideoFrame &frame)
{
    // printf("send frame \n");

    d->session->networkDetection()->startBandwidthMeasure();

    d->frameId++;

    peer_draw_background(d->session->rdpPeer(),frame);
    d->session->networkDetection()->stopBandwidthMeasure();

    
}

}
